/*
? 1. 原型链继承：父类的实例作为子类的原型
? 2. 构造函数继承：子类执行父类方法，没有原型
? 3. 组合继承：父类的实例作为子类的原型，子类执行父类方法
? 4. 原型式继承：Object.create,创造一个对象，其原型执行传入的参数，
? 5. 寄生式继承：在原型式继承基础上，封装了一下添加了些新东西
? 6. 寄生组合继承：最好的方式
? 7.

 */


//? 1.原型链继承 核心： 将父类的实例作为子类的原型
function Parent1() {
    this.name = 'parent1';
    this.play = [1, 2, 3]
}
function Child1() {
    this.type = 'child2';
}
Child1.prototype = new Parent1();
console.log(new Child1());

//? 潜在的问题
let s1 = new Child1();
let s2 = new Child1();
s1.play.push(4);
console.log(s1.play, s2.play); //? [1,2,3,4]   [1,2,3,4]
//? 两个实例使用的是同一个原型对象（父的一个实例）。它们的内存空间是共享的，当一个发生变化的时候，另外一个也随之进行了变化，这就是使用原型链继承方式的一个缺点。

//? 2、构造函数继承（借助 call） 核心：使用父类的构造函数来增强子类实例，等于是复制父类的实例属性给子类（没用到原型）
function Parent1(){
    this.name = 'parent1';
}

Parent1.prototype.getName = function () {
    return this.name;
}

function Child1(){
    Parent1.call(this);
    this.type = 'child1'
}

let child = new Child1();
console.log(child);  //? 没问题
console.log(child.getName());  //? 会报错

//? 3、组合继承（前两种组合）核心：通过调用父类构造，继承父类的属性并保留传参的优点，然后通过将父类实例作为子类原型，实现函数复用
function Parent3 () {
    this.name = 'parent3';
    this.play = [1, 2, 3];
}

Parent3.prototype.getName = function () {
    return this.name;
}
function Child3() {
    //? 第二次调用 Parent3() 这也是缺点所在
    Parent3.call(this);
    this.type = 'child3';
}

//? 第一次调用 Parent3()
Child3.prototype = new Parent3();
//? 手动挂上构造器，指向自己的构造函数 避免.constructor判断类型时找到老父亲了
//? 将构造函数挂载到原型上避免了为子类的每个实例复制构造函数
Child3.prototype.constructor = Child3;
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play);  //? 不互相影响
console.log(s3.getName()); //? 正常输出'parent3'
console.log(s4.getName()); //? 正常输出'parent3'
console.log(s3.constructor) //? 如果不挂构造器这段结果将是Parent3

//? 4、原型式继承 ES5 里面的 Object.create 方法，这个方法接收两个参数：一是用作新对象原型的对象、二是为新对象定义额外属性的对象（可选参数）
//? Object.create是 JavaScript 中一个用于创建新对象的方法。它接收一个参数，用作新对象的原型（prototype）
let parent4 = {
    name: "parent4",
    friends: ["p1", "p2", "p3"],
    getName: function() {
        return this.name;
    }
};

let person4 = Object.create(parent4);
person4.name = "tom";  //? 特别注意！这会在create后的空对象中创建一个name,而不会修改原型链上的name
person4.friends.push("jerry");//? 特别注意！如果操作数组(包括其他对象),就会修改原型链上的.即用点操作必然会在自己身上创
let person44 = Object.create(parent4);
person44.friends.push("lucy");

console.log(person4.name); //? tom
console.log(person4.name === person4.getName()); //? true
console.log(person44.name); //? parent4
console.log(person4.friends); //? ['p1', 'p2', 'p3', 'jerry', 'lucy']
console.log(person44.friends); //? ['p1', 'p2', 'p3', 'jerry', 'lucy']

//? 5、寄生式继承 使用原型式继承可以获得一份目标对象的浅拷贝，然后利用这个浅拷贝的能力再进行增强，添加一些方法，这样的继承方式就叫作寄生式继承。
let parent5 = {
    name: "parent5",
    friends: ["p1", "p2", "p3"],
    getName: function() {
        return this.name;
    }
};

function clone(original) {
    let clone = Object.create(original);
    clone.getFriends = function() {
        return this.friends
    };
    return clone;
}

let person5 = clone(parent5);
console.log(person5.getName()); //? parent5
console.log(person5.getFriends()); //? ['p1', 'p2', 'p3']

//? 6、寄生组合式继承 结合第四种中提及的继承方式，解决普通对象的继承问题的 Object.create 方法，我们在前面这几种继承方式的优缺点基础上进行改造，得出了寄生组合式的继承方式，
//? 这也是所有继承方式里面相对最优的继承方式。
//?        Parent6.prototype
//?      {}            Parent6
//?children  Children6
function clone2 (parent, child) {
    //? 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
    child.prototype = Object.create(parent.prototype);
    //? 这里原因在于使用 Object.create 修改子类的原型的 __proto__，从而导致子类的构造函数变更为父类的构造函数，这样会导致 instanceof 判断错误。
    child.prototype.constructor = child;
}

function Parent6() {
    this.name = 'parent6';
    this.play = [1, 2, 3];
}
Parent6.prototype.getName = function () {
    return this.name;
}
function Child6() {
    Parent6.call(this);
    this.friends = 'child5';
}

clone2(Parent6, Child6);

Child6.prototype.getFriends = function () {
    return this.friends;
}

let person6 = new Child6();
console.log(person6);  //? child6 {name: "parent6",play: [1, 2, 3], friends: "child5"}
console.log(person6.getName()); //? parent6
console.log(person6.getFriends()); //? child5

//? 7、class extend 也就是寄生组合式继承的语法糖
class Person {
    constructor(name) {
        this.name = name
    }
    //? 原型方法
    //? 即 Person.prototype.getName = function() { }
    //? 下面可以简写为 getName() {...}
    getName = function () {
        console.log('Person:', this.name)
    }
}

class Gamer extends Person {
    constructor(name, age) {
        //? 子类中存在构造函数，则需要在使用“this”之前首先调用 super()。
        super(name)
        this.age = age
    }
}

const asuna = new Gamer('Asuna', 20)
asuna.getName() //? 成功访问到父类的方法
